package com.luych.oauth2Server;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luych.common.constant.UserMessageConstant;
import com.luych.oauth2Server.entity.Oauth2Client;
import com.luych.oauth2Server.entity.Oauth2Scope;
import com.luych.oauth2Server.entity.UserInfo;
import com.luych.oauth2Server.service.Oauth2Service;
import com.luych.oauth2Server.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@EnableAuthorizationServer
@SpringCloudApplication
@EnableCaching
@MapperScan("com.luych.oauth2Server.dao")
public class Oauth2ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Oauth2ServerApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService(UserService userService) {
        return loginName -> {
            UserInfo userInfo;
            try {
                userInfo = userService.getUserByLoginName(loginName);
            } catch (IOException e) {
                throw new UsernameNotFoundException("查询用户信息失败", e);
            }
            if (userInfo == null) {
                throw new UsernameNotFoundException("username无效");
            }
            return User.withUsername(loginName).password(userInfo.getPassWord()).roles("").build();
        };
    }

    @Bean
    public AuthenticationEntryPoint loginUrlAuthenticationEntryPoint() {
        LoginUrlAuthenticationEntryPoint AuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
        AuthenticationEntryPoint.setUseForward(true);
        return AuthenticationEntryPoint;
    }

    @Bean
    public RequestCache requestCache() {
        return new HttpSessionRequestCache();
    }

    @Bean
    public ExceptionTranslationFilter exceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) {
        return new ExceptionTranslationFilter(authenticationEntryPoint, requestCache);
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            response.setStatus(HttpStatus.OK.value());
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(UserMessageConstant.message_LoginSuccess);
            response.getWriter().flush();
            response.getWriter().close();
        };
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(UserMessageConstant.error_UsernameOrPassword);
            response.getWriter().flush();
            response.getWriter().close();
        };
    }

    @Bean
    public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
        return new WebSecurityConfigurerAdapter() {
            @Override
            public void configure(HttpSecurity httpSecurity) throws Exception {
                httpSecurity.formLogin().successHandler(successHandler).failureHandler(failureHandler).
                        and().csrf().disable();
            }
        };
    }

    @Bean
    public ClientDetailsService clientDetailsService(Oauth2Service oauth2Service) {
        return clientId -> {
            if (StringUtils.isEmpty(clientId)) {
                throw new ClientRegistrationException("clientId不能为空");
            }
            Oauth2Client oauth2Client = oauth2Service.getByClientId(clientId);
            if (oauth2Client == null) {
                throw new ClientRegistrationException("clientId无效");
            }
            BaseClientDetails clientDetails = new BaseClientDetails();
            clientDetails.setClientId(oauth2Client.getClientId());
            clientDetails.setClientSecret(oauth2Client.getClientSecret());
            clientDetails.setRegisteredRedirectUri(new HashSet<>(Arrays.asList(oauth2Client.getRedirectUrl().split(","))));
            clientDetails.setAuthorizedGrantTypes(Arrays.asList(oauth2Client.getGrantType().split(",")));

            List<Oauth2Scope> scopeList = oauth2Service.getScopeByClientId(clientId);
            List<String> scopes = new ArrayList<>(scopeList.size());
            if (scopeList.size() > 0) {
                for (Oauth2Scope scope : scopeList) {
                    scopes.add(scope.getScopeName());
                }
            }
            clientDetails.setScope(scopes);
            return clientDetails;
        };
    }

    @Bean
    public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, OAuth2Authentication> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();

        return new RandomValueAuthorizationCodeServices() {

            @Override
            protected void store(String code, OAuth2Authentication authentication) {
                redisTemplate.boundValueOps(code).set(authentication, 10, TimeUnit.MINUTES);
            }

            @Override
            protected OAuth2Authentication remove(String code) {
                OAuth2Authentication authentication = redisTemplate.boundValueOps(code).get();
                redisTemplate.delete(code);
                return authentication;
            }
        };
    }

    @Bean
    public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(Collections.singletonList(provider));
    }

    @Bean
    public AuthorizationServerConfigurer authorizationServerConfigurer(UserDetailsService userDetailsService, ClientDetailsService clientDetailsService, TokenStore tokenStore, AuthorizationCodeServices authorizationCodeServices, AuthenticationManager authenticationManager) {
        return new AuthorizationServerConfigurer() {
            @Override
            public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

            }

            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.withClientDetails(clientDetailsService);
            }

            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                endpoints.userDetailsService(userDetailsService);
                endpoints.tokenStore(tokenStore);
                endpoints.authorizationCodeServices(authorizationCodeServices);
                endpoints.authenticationManager(authenticationManager);
            }
        };
    }
}
